A proposta consiste em analisar e prevero preço das estadias Airbnb na cidade do Rio de Janeiro através do agrupamento de caracaterísticas físicas e de localização dos imóveis utilizando K-means e posteriormente para os clusters gerados aplicam-se técnicas de séries temporais (aqui aplicadas média móvel e suaviazação exponencial simples) e medidas através dos erro MAPE - Erro absoluto médio percentual para verificar a acurácia da previsão. Não foi objeto de análise verificar as informações do host bem com scores de pontuação do host ou da localidad visto que podem ser consideradas aspectos mais subjetivos de comportamento humano, necessitando de outros tipos de análise (como análise de sentimentos).
Out[3]:
|
id |
listing_url |
scrape_id |
last_scraped |
name |
description |
neighborhood_overview |
picture_url |
host_id |
host_url |
... |
review_scores_communication |
review_scores_location |
review_scores_value |
license |
instant_bookable |
calculated_host_listings_count |
calculated_host_listings_count_entire_homes |
calculated_host_listings_count_private_rooms |
calculated_host_listings_count_shared_rooms |
reviews_per_month |
| 0 |
17878 |
https://www.airbnb.com/rooms/17878 |
20210126045954 |
2021-01-27 |
Very Nice 2Br in Copacabana w. balcony, fast WiFi |
Discounts for long term stays. <br />- Large b... |
This is the one of the bests spots in Rio. Bec... |
https://a0.muscache.com/pictures/65320518/3069... |
68997 |
https://www.airbnb.com/users/show/68997 |
... |
10.0 |
10.0 |
9.0 |
NaN |
t |
1 |
1 |
0 |
0 |
2.01 |
| 1 |
25026 |
https://www.airbnb.com/rooms/25026 |
20210126045954 |
2021-01-27 |
Beautiful Modern Decorated Studio in Copa |
Our apartment is a little gem, everyone loves ... |
Copacabana is a lively neighborhood and the ap... |
https://a0.muscache.com/pictures/3003965/68ebb... |
3746246 |
https://www.airbnb.com/users/show/3746246 |
... |
10.0 |
10.0 |
9.0 |
NaN |
f |
11 |
11 |
0 |
0 |
1.84 |
2 rows × 74 columns
Out[4]:
|
listing_id |
date |
available |
price |
adjusted_price |
minimum_nights |
maximum_nights |
| 0 |
6213104 |
2021-01-27 |
t |
$244.00 |
$244.00 |
1.0 |
1125.0 |
| 1 |
6213104 |
2021-01-28 |
t |
$244.00 |
$244.00 |
1.0 |
1125.0 |
Removendo colunas não interessantes à análise. Resumidamente, foram desconsiradas aqui informações sobre o host, disponibilidade do imóvel, os links de acesso ao site Airbnb e detalhes sobre avaliações do imóvel e do host.
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 9529874 entries, 0 to 9529873
Data columns (total 7 columns):
# Column Dtype
--- ------ -----
0 listing_id int64
1 date datetime64[ns]
2 available int64
3 price float64
4 adjusted_price float64
5 minimum_nights float64
6 maximum_nights float64
dtypes: datetime64[ns](1), float64(4), int64(2)
memory usage: 509.0 MB
Out[30]:
|
listing_id |
available |
price |
adjusted_price |
minimum_nights |
maximum_nights |
| count |
9.529874e+06 |
9529874.0 |
9.529681e+06 |
9.529681e+06 |
9.528347e+06 |
9.528347e+06 |
| mean |
2.481193e+07 |
0.0 |
1.010015e+03 |
1.008949e+03 |
4.835935e+00 |
3.904977e+04 |
| std |
1.551999e+07 |
0.0 |
9.894297e+03 |
9.892788e+03 |
2.038694e+01 |
6.189122e+06 |
| min |
1.787800e+04 |
0.0 |
0.000000e+00 |
0.000000e+00 |
1.000000e+00 |
1.000000e+00 |
| 25% |
1.206936e+07 |
0.0 |
1.600000e+02 |
1.600000e+02 |
1.000000e+00 |
9.000000e+01 |
| 50% |
2.304177e+07 |
0.0 |
2.950000e+02 |
2.950000e+02 |
2.000000e+00 |
1.125000e+03 |
| 75% |
4.056471e+07 |
0.0 |
5.920000e+02 |
5.910000e+02 |
4.000000e+00 |
1.125000e+03 |
| max |
4.789324e+07 |
0.0 |
2.187712e+06 |
2.187712e+06 |
1.111000e+03 |
1.000000e+09 |
0.9974 0.9974 0.9969 0.996 0.9926 0.9831 0.9599 0.8574 0.6917 0.3248
Podemos observar que abaixo de 2500 podemos reduzir o dataframe para 95,99% do tamanho original.
Ainda mantém-se outliers (95,99-75 ~ 22%) que podem não ser outliers (a depender da relação de outros atributos
com o preço) vide exemplo "https://www.airbnb.com.br/rooms/17717006?adults=2&check_in=2021-07-12&check_out=2021-07-18&federated_search_id=f508bb21-aa8e-4bdf-9e74-d63907edb479&source_impression_id=p3_1614992529_VgnWDwRQLVXqbbyZ&guests=1".
C:\Users\tarci\anaconda3\lib\site-packages\ipykernel_launcher.py:1: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
"""Entry point for launching an IPython kernel.
C:\Users\tarci\anaconda3\lib\site-packages\ipykernel_launcher.py:2: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
C:\Users\tarci\anaconda3\lib\site-packages\ipykernel_launcher.py:3: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
This is separate from the ipykernel package so we can avoid doing imports until
C:\Users\tarci\anaconda3\lib\site-packages\ipykernel_launcher.py:4: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
after removing the cwd from sys.path.
C:\Users\tarci\anaconda3\lib\site-packages\ipykernel_launcher.py:5: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
"""
Out[89]:
Text(0.5, 1.0, 'Preço médio por dia do mês')
Out[90]:
Text(0.5, 1.0, 'Preço médio por dia da semana')
Out[86]:
Text(0.5, 1.0, 'Preço médio por dia do ano')
Out[57]:
Text(0.5, 1.0, 'Preço médio por mês')
Out[58]:
Text(0.5, 1.0, 'Preço médio por semana')
Podemos observar que o preço é influenciado pelo período do tempo. No geral, os preços são maiores no início do ano (especialmente fevereiro e com picos em Julho, Setembro e Dezembro/Janeiro), entre os dias 10 a 18 do mês e no final de semana (sexta e sábado).
Podemos observar que não existe uma relação entre mínimo/máximo de estadia com o preço e portanto desconsiderados na análise do preço. Até o momento, a variável tempo (subdividida em dia da semana, dia do mês, dia do ano, mês e semana) foi a que mostrou padrão de relacionamento com a variável preço. Agora faremos a análise com o dataset listings com os atributos do imóvel.
Out[99]:
Text(0.5, 1.0, 'Histograma variável preço')
Out[101]:
count 9.175259e+06
mean 4.342591e+02
std 4.325488e+02
min 0.000000e+00
25% 1.560000e+02
50% 2.800000e+02
75% 5.100000e+02
max 2.500000e+03
Name: price, dtype: float64
75% dos preços concentram-se em até $510 e,em média, a diária no Rio de Janeiro é de $280.
C:\Users\tarci\anaconda3\lib\site-packages\ipykernel_launcher.py:1: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
"""Entry point for launching an IPython kernel.
C:\Users\tarci\anaconda3\lib\site-packages\pandas\core\generic.py:5303: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
self[name] = value
C:\Users\tarci\anaconda3\lib\site-packages\ipykernel_launcher.py:4: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
after removing the cwd from sys.path.
C:\Users\tarci\anaconda3\lib\site-packages\ipykernel_launcher.py:4: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
after removing the cwd from sys.path.
C:\Users\tarci\anaconda3\lib\site-packages\ipykernel_launcher.py:5: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
"""
C:\Users\tarci\anaconda3\lib\site-packages\ipykernel_launcher.py:6: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
Out[175]:
Text(0.5, 1.0, 'Preço médio por bairro')
Out[180]:
neighbourhood_cleansed
Vila Kosmos 39.000000
Coelho Neto 81.250000
Galeão 84.000000
Senador Vasconcelos 84.500000
Jardim Carioca 93.333333
...
Cacuia 795.000000
Anchieta 800.000000
Vaz Lobo 822.000000
Cavalcanti 925.000000
Vista Alegre 1304.000000
Name: price, Length: 150, dtype: float64
Out[182]:
Text(0.5, 1.0, 'Preço médio por patrimônio')
Out[183]:
property_type
Private room in boat 52.0
Private room in tent 54.0
Shared room in casa particular 56.0
Shared room in townhouse 60.0
Shared room in chalet 62.0
...
Boat 1000.0
Casa particular 1010.8
Private room in castle 1200.0
Houseboat 1280.0
Entire vacation home 2500.0
Name: price, Length: 84, dtype: float64
Out[186]:
Text(0.5, 1.0, 'Preço médio por tipo de imóvel')
Out[187]:
room_type
Shared room 204.331070
Private room 252.125801
Hotel room 267.356322
Entire home/apt 528.563806
Name: price, dtype: float64
A visualização acima nos permite verificar que há relação de preço com o bairro, tipo de propriedade e com o tipo de imóvel (aqui mais evidente que entire home/apt possui maior preço comparado aos demais - com preços próximos)
Observamos também correlação do preço com a característica de acomodações, quantida de banheiros e quartos. Com as características a serem consideradas para o modelo, podemos uní-las em um único dataframe.
C:\Users\tarci\anaconda3\lib\site-packages\pandas\core\frame.py:4133: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
errors=errors,
Out[27]:
|
accommodates |
bathrooms_text |
bedrooms |
cat_bairro |
cat_patrimonio |
cat_imovel |
dia |
mes |
semana |
dia_ano |
dia_semana |
Clusters |
price |
| 0 |
0.371907 |
-0.669171 |
0.370928 |
-0.588235 |
-0.376648 |
-0.634493 |
1.282072 |
-1.602246 |
-1.491855 |
-1.480200 |
-0.498828 |
6 |
201.0 |
| 1 |
0.371907 |
-0.669171 |
0.370928 |
-0.588235 |
-0.376648 |
-0.634493 |
1.395760 |
-1.602246 |
-1.491855 |
-1.470705 |
0.001622 |
6 |
201.0 |
| 2 |
0.371907 |
-0.669171 |
0.370928 |
-0.588235 |
-0.376648 |
-0.634493 |
1.509447 |
-1.602246 |
-1.491855 |
-1.461210 |
0.502072 |
6 |
201.0 |
| 3 |
0.371907 |
-0.669171 |
0.370928 |
-0.588235 |
-0.376648 |
-0.634493 |
1.623134 |
-1.602246 |
-1.491855 |
-1.451715 |
1.002522 |
6 |
201.0 |
| 4 |
0.371907 |
-0.669171 |
0.370928 |
-0.588235 |
-0.376648 |
-0.634493 |
1.736821 |
-1.602246 |
-1.491855 |
-1.442220 |
1.502972 |
6 |
201.0 |
Out[28]:
|
accommodates |
bathrooms_text |
bedrooms |
cat_bairro |
cat_patrimonio |
cat_imovel |
dia |
mes |
semana |
dia_ano |
dia_semana |
price |
| Clusters |
|
|
|
|
|
|
|
|
|
|
|
|
| 0 |
-0.057797 |
0.007080 |
-0.624059 |
0.295167 |
3.203364 |
2.268505 |
0.000032 |
-0.000411 |
-0.000201 |
-0.000407 |
0.000546 |
234.480521 |
| 1 |
0.156229 |
0.121648 |
0.256150 |
0.966268 |
-0.560261 |
-0.634493 |
-0.000135 |
0.000297 |
0.000304 |
0.000287 |
0.000163 |
558.944803 |
| 2 |
-0.328699 |
-0.591476 |
-0.619078 |
-0.457341 |
-0.552453 |
-0.634493 |
0.000103 |
-0.000625 |
-0.000505 |
-0.000609 |
-0.000007 |
311.463010 |
| 3 |
-0.813414 |
-0.402055 |
-0.605086 |
-0.405613 |
1.087683 |
1.429862 |
-0.000092 |
0.000677 |
0.000884 |
0.000671 |
0.000090 |
247.897761 |
| 4 |
3.253372 |
3.984041 |
3.669641 |
0.201041 |
0.106930 |
-0.313603 |
0.000467 |
-0.000440 |
-0.001555 |
-0.000456 |
-0.001468 |
1219.765700 |
| 5 |
-0.752986 |
-0.367634 |
-0.568778 |
1.835486 |
1.272928 |
1.454507 |
-0.000141 |
0.000790 |
0.001037 |
0.000781 |
0.000454 |
238.847798 |
| 6 |
0.488275 |
0.259150 |
0.548015 |
-0.800316 |
-0.601190 |
-0.631875 |
-0.000061 |
-0.000042 |
-0.000196 |
-0.000048 |
-0.000048 |
565.034833 |
| 7 |
1.437845 |
1.346564 |
1.533184 |
-0.029185 |
-0.546272 |
-0.591854 |
0.000219 |
-0.000399 |
-0.000944 |
-0.000398 |
-0.000584 |
873.488397 |
Out[29]:
<matplotlib.axes._subplots.AxesSubplot at 0x260239066c8>
Previsão por séries temporais. definição do erro MAPE, média móvel (mm) e suavização exponencial (ses).
Calculando as previsões para Dia+1 e respectivos erros MAPE
Pode ser claramente observado o efeito de inicio da pandemia no mês de Fevereiro no Brasil com a queda brusca de locações e consequentemente do preço (lei oferta/demanda) na maioria dos clusters e também nos tipos de locação e, apesar de no gráfico mostrar uma queda acentuada no preço, podemos constatar que o intervalo de variação do preço é pequeno (vide valores eixo vertical de cada gráfico). Abaixo verifica-se a estacionaridade de cada série a partir do teste adfuller que, para valores abaixo de 0.05 a série é estacionária e portando testes de previsão podem ser aplicados na série e, caso contrário, é feita uma diferenciação para tornar a série estacionária (se for possível). Sendo a série não estacionária, novas diferenciações podem ser feitas mas a perda de informação se torna maior. Neste projeto, optou-se por realizar somente 1 diferenciação e, em caso negativo de estacionaridade, a série é desconsiderada.
Entire home/apt: estacionaria com diferenciacao
Hotel room: estacionaria com diferenciacao
Private room: estacionaria com diferenciacao
Shared room: estacionaria com diferenciacao
Cluster 0: estacionaria com diferenciacao
Cluster 1: estacionaria com diferenciacao
Cluster 2: estacionaria com diferenciacao
Cluster 3: estacionaria
Cluster 4: estacionaria com diferenciacao
Cluster 5: estacionaria
Cluster 6: estacionaria com diferenciacao
Cluster 7: estacionaria com diferenciacao
hotel room: 0.03 (ses 0.9)
Conclusão: Pode ser observado que as medidas de erro MAPE foram baixas para os grupos de imóvel já disponibilizados nas bases de dados. O método de duavização simples foi o que apresentou o melhor resultado com MAPE de 0.01. Na clusterização foram obtidos os mesmos índices de erro MAPE porém, no total de erros, a clusterização se mostrou melhor com erros próximos a 0.1 em todos os métodos considerados enquando no agrupamento por imóveis este erro ficou próximo de 0.4. A clusterização se mostra promissora para resultados de previsão de preços do Airbnb visto que há uma gama elevada de atributos do imóvel e do host que podem ser consideradas para aumentar a aproximação dos modelos à relidade do problema.